import java.awt.Color;
import java.util.ArrayList;
import java.util.Enumeration;

import javax.media.j3d.*;
import javax.vecmath.*;

import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;

/** Tegner båndene til molekylet
 *
 *	@author Alexander Vrtis, Andreas Leknes, Steffan Sørenes
 */
public class Helix extends Shape3D {
	//Feltvariabler:
	private GeometryInfo geom_band1;
	private GeometryInfo geom_band2;
	private static final Color3f band1_color = new Color3f(0.3f, 0.3f, 0.9f);
	private static final Color3f band2_color = new Color3f(0.3f, 0.3f, 0.9f);
	private static final float rad = 0.8f;
	public float band_thickness = 0.05f;
	public float r_offset = 0.7432f;
	public Transform3D transform;
	public Point3f[] allpoints;

	public Helix() {
		transform = new Transform3D();
	}


	private Color3f[] getColorArray(int count, Color3f color) {
		Color3f[] colors = new Color3f[count];
		for (int i = 0; i < count; i++) {
			colors[i] = color;
		}
		return colors;
	}


	/**
	 * 
	 * Denne funksjonen gir en array av Point3f koordinater som tilsvarer en
	 * spiral
	 * 
	 * @param numRevolutions -
	 *            Kor mongen gonger helixen ska snurra.
	 * @param heightPerRevolution
	 * @param subdivisions
	 * @param radoffset
	 * @param heightoffset
	 * @return
	 */
	private Point3f[] getControlPoints(float numRevolutions,
			float heightPerRevolution, int subdivisions, float radoffset,
			float heightoffset, float radius) 
	{
		float radInterval = (float) (2 * Math.PI) / (float) subdivisions;
		int numpoints = (int) (numRevolutions * subdivisions);

		Point3f[] ctrlPts = new Point3f[numpoints];

		float radpos = radoffset;
		float heightpos = 0.0f;
		float maxheight = heightPerRevolution * numRevolutions;
		float heightInterval = maxheight / numpoints;

		for (int i = 0; i < numpoints; i++) {
			ctrlPts[i] = new Point3f((float) (radius * Math.sin(radpos)),
					heightoffset - (maxheight / 2.0f) + heightpos,
					(float) (radius * Math.cos(radpos)));
			radpos += radInterval;
			heightpos += heightInterval;
		}

		return ctrlPts;
	}

	/**
	 * Legger sammen de to spiralene og gir ut en liste av punkt som danner
	 * polygoner.
	 * 
	 * Virker ikke skikkelig enda.
	 * 
	 * @param numRevolutions
	 * @param heightPerRevolution
	 * @param subdivisions
	 * @param radoffset
	 * @param heightoffset
	 * @return
	 */
	private Point3f[] getControlPoints2(float numRevolutions,
			float heightPerRevolution, int subdivisions, float radoffset,
			float heightoffset, float radius)
	{
		int numpoints = (int) (numRevolutions * subdivisions);
		float hOff = heightoffset;
		float rOff = radoffset;
		Point3f[] ctrlPts = new Point3f[numpoints * 8 + 8];
		Point3f[] linestrip1 = getControlPoints(numRevolutions,
				heightPerRevolution, subdivisions, radoffset, hOff, radius);
		Point3f[] linestrip2 = getControlPoints(numRevolutions,
				heightPerRevolution, subdivisions, radoffset, hOff + .3f,
				radius);
		Point3f[] linestrip3 = getControlPoints(numRevolutions,
				heightPerRevolution, subdivisions, radoffset, hOff, radius
						+ band_thickness);
		Point3f[] linestrip4 = getControlPoints(numRevolutions,
				heightPerRevolution, subdivisions, radoffset, hOff + .3f,
				radius + band_thickness);
		int count = 0;

		for (int i = 0; i < numpoints; i++) {
			ctrlPts[count++] = linestrip1[i];
			ctrlPts[count++] = linestrip2[i];
		}

		for (int i = 0; i < numpoints; i++) {
			ctrlPts[count++] = linestrip4[i];
			ctrlPts[count++] = linestrip3[i];
		}

		for (int i = 0; i < numpoints; i++) {
			ctrlPts[count++] = linestrip2[i];
			ctrlPts[count++] = linestrip4[i];
		}

		for (int i = 0; i < numpoints; i++) {
			ctrlPts[count++] = linestrip3[i];
			ctrlPts[count++] = linestrip1[i];
		}

		int start = 0;
		int stop = linestrip1.length - 1;

		ctrlPts[count++] = linestrip1[start];
		ctrlPts[count++] = linestrip3[start];
		ctrlPts[count++] = linestrip2[start];
		ctrlPts[count++] = linestrip4[start];

		ctrlPts[count++] = linestrip1[stop];
		ctrlPts[count++] = linestrip2[stop];
		ctrlPts[count++] = linestrip3[stop];
		ctrlPts[count++] = linestrip4[stop];

		return ctrlPts;
	}

	private void addCylinder(Point3f top, Point3f bottom, float cylradius,
			int subdiv, Color3f color) {
		Point3f[] vertices = new Point3f[2 + subdiv * 2];

		// Hvor mange radinar mellom hver sylinder
		float interval = (float) (2.0f * Math.PI) / (float) subdiv;
		// Posisjon i rotasjon
		float position = 0.0f;
		// Lengde på sylinder
		float length = top.distance(bottom) / 2.0f;

		// Legg til punkter for sylinderen
		for (int i = 0; i < (subdiv * 2);) {
			vertices[i++] = new Point3f(cylradius * (float) Math.sin(position),
					cylradius * (float) Math.cos(position), 0.0f);
			vertices[i++] = new Point3f(cylradius * (float) Math.sin(position),
					cylradius * (float) Math.cos(position), length);

			position += interval;
		}

		// Legg til to punkt for å "sveise" samme sylinderen
		vertices[subdiv * 2] = new Point3f(cylradius * (float) Math.sin(0),
				cylradius * (float) Math.cos(0), 0.0f);
		vertices[subdiv * 2 + 1] = new Point3f(cylradius * (float) Math.sin(0),
				cylradius * (float) Math.cos(0), length);

		// z vektor som sylindrene ligger langs som standard
		Vector3f zvec = new Vector3f(0.0f, 0.0f, 1.0f);
		// Vektor so sylindrene skal ligge langs for å være posisjonert riktig
		Vector3f destv = new Vector3f(top.x - bottom.x, top.y - bottom.y, top.z
				- bottom.z);
		// Kryssproduktet mellom de to vektorene som danner aksen sylindrene
		// skal roteres rundt
		Vector3f rotax = new Vector3f();
		rotax.cross(zvec, destv);

		// Rotasjonen
		AxisAngle4f aa = new AxisAngle4f(rotax, destv.angle(zvec));

		Matrix4f m = new Matrix4f();
		m.setIdentity();
		m.set(aa);

		Matrix4d mat = new Matrix4d();
		mat.setIdentity();
		mat.set(new Vector3d(bottom));

		for (int i = 0; i < vertices.length; i++) {
			m.transform(vertices[i]);
			mat.transform(vertices[i]);
		}

		// The last step. Convert vertices to geometry and add it to the helix
		// shape.
		int[] cc = { subdiv * 2 + 2 };
		int t = cc[0];

		Color3f[] colors = new Color3f[t];
		for (int i = 0; i < colors.length; i++) {
			colors[i] = color;
		}
		GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY);
		gi.setCoordinates(vertices);
		gi.setColors(colors);
		gi.setStripCounts(cc);

		// Genererer normaler til båndet
		NormalGenerator ng = new NormalGenerator();
		ng.generateNormals(gi);

		this.addGeometry(gi.getGeometryArray());

	}

	public void createGeometry(Color3f[] cc1, Color3f[] cc2) throws Exception {
		if (cc1.length != cc2.length)
			throw new Exception("cc1.length != cc2.length");

		int num_cylinders = cc1.length;
		float cylindersperrevolution = 8;
		// Hvor mange ganger helixen ska "snurre":
		float revolutions = num_cylinders / cylindersperrevolution; 
		// Antall punkt per 360grader/2pi radianer:
		int subdiv = 60; 
		// Antall koordinater i en stk SPIRAL, ikke bånd:
		int num_vertices_spiral = (int) (revolutions * subdiv); 

		this.removeAllGeometries();

		// Generere alle punktene som danne trekanter i trianglestrip-en
		Point3f[] band1_vertices = getControlPoints2(revolutions, 2.0f, subdiv,
				(float) Math.PI, 0.0f, rad);
		Point3f[] band2_vertices = getControlPoints2(revolutions, 2.0f, subdiv,
				r_offset, 0.0f, rad);

		int[] vertex_count_per_strip = { num_vertices_spiral * 2,
				num_vertices_spiral * 2, num_vertices_spiral * 2,
				num_vertices_spiral * 2, 4, 4 };

		// Alt om bånd1 her
		geom_band1 = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY);
		geom_band1.setCoordinates(band1_vertices);
		geom_band1.setStripCounts(vertex_count_per_strip);
		geom_band1.setColors(getColorArray(num_vertices_spiral * 8 + 8,
				band1_color));
		geom_band1.getGeometryArray().setCapability(GeometryArray.BY_REFERENCE);

		// Alt om bånd2 her
		geom_band2 = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY);
		geom_band2.setCoordinates(band2_vertices);
		geom_band2.setStripCounts(vertex_count_per_strip);
		geom_band2.setColors(getColorArray(num_vertices_spiral * 8 + 8,
				band2_color));
		geom_band2.getGeometryArray().setCapability(GeometryArray.BY_REFERENCE);

		// Enklaste måte å få generert normalar på. Normalar trengs for
		// å få skikkelig lys
		NormalGenerator ng = new NormalGenerator();
		ng.setCreaseAngle(1.1f);
		ng.generateNormals(geom_band1);
		ng.generateNormals(geom_band2);

		this.addGeometry(geom_band1.getGeometryArray());
		this.addGeometry(geom_band2.getGeometryArray());

		// "Festepunkter" for sylindrer
		Point3f[] pts11 = getControlPoints(revolutions, 2.0f,
				(int) cylindersperrevolution, (float) Math.PI, 0.15f,
				rad - 0.007f);
		Point3f[] pts12 = getControlPoints(revolutions, 2.0f,
				(int) cylindersperrevolution, r_offset, 0.15f, rad - 0.007f);

		for (int i = 1; i < pts11.length; i++) {
			addCylinder(pts11[i], pts12[i], 0.08f, 10, cc1[i]);
			addCylinder(pts12[i], pts11[i], 0.08f, 10, cc2[i]);
		}

		for (Enumeration en = this.getAllGeometries(); en.hasMoreElements();) {
			GeometryArray ga = (GeometryArray) en.nextElement();
		}
	}

}
